package com.tarena.lbs.content.dao.repository.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.TotalHits;
import co.elastic.clients.json.JsonData;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tarena.lbs.base.protocol.pager.PageResult;
import com.tarena.lbs.content.dao.mapper.ArticleMapper;
import com.tarena.lbs.content.dao.repository.ActionESRepository;
import com.tarena.lbs.content.dao.repository.ArticleESRepository;
import com.tarena.lbs.content.dao.repository.ArticleRepository;
import com.tarena.lbs.content.pojo.po.ActionSearchPO;
import com.tarena.lbs.content.pojo.po.ArticleSearchPO;
import com.tarena.lbs.content.pojo.query.ArticleQuery;
import com.tarena.lbs.content.pojo.vo.ArticleActionVO;
import com.tarena.lbs.content.pojo.vo.ArticleStatsVO;
import com.tarena.lbs.content.pojo.vo.ArticleVO;
import com.tarena.lbs.content.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Repository
@Slf4j
public class ArticleRepositoryImpl implements ArticleRepository {
  @Autowired
  private ArticleMapper articleMapper;

  // 使用@Lazy和required = false来延迟加载并允许缺失
  @Autowired(required = false)
  private ArticleESRepository articleESRepository;

  @Autowired(required = false)
  private ActionESRepository actionESRepository;

  @Autowired(required = false)
  private ElasticsearchClient elasticsearchClient;

  @DubboReference
  private RedisService redisService;

  @Override
  public ArticleSearchPO saveArticle(ArticleSearchPO article) {
    // 如果ES不可用，则只保存到数据库
    if (articleESRepository != null) {
      return articleESRepository.save(article);
    } else {
      log.warn("Elasticsearch不可用，文章只保存到数据库");
      // 这里应该调用数据库保存方法
      return article;
    }
  }

  //PageResult<ArticleVO>findArticle(ArticleQuery query);
  @Override
  public PageResult<ArticleVO> findArticle(ArticleQuery query) {
    log.debug("开始从ES分页查询文章: {}", query);
    PageResult<ArticleVO> voPage = new PageResult<>();
    
    // 如果ES客户端不可用，返回空结果
    if (elasticsearchClient == null) {
      log.warn("Elasticsearch客户端不可用，返回空结果");
      voPage.setObjects(Collections.emptyList());
      voPage.setTotal(0L);
      voPage.setPageNo(query.getPageNo());
      voPage.setPageSize(query.getPageSize());
      return voPage;
    }

    BoolQuery.Builder boolQueryBuilder = buildArticleQuery(query);
    Query finalQuery = boolQueryBuilder.build()._toQuery();

    try {
      // 2. 执行搜索查询
      SearchResponse<ArticleVO> response = elasticsearchClient.search(s -> s
                      .index("lbs_article") // 指定要查询的索引
                      .query(finalQuery)   // 应用构建好的查询条件
                      .from((query.getPageNo() - 1) * query.getPageSize())
                      .size(query.getPageSize())
                      .sort(so -> so.field(f -> f.field("createTime").order(SortOrder.Desc))),
              ArticleVO.class
      );

      TotalHits total = response.hits().total();
      long totalHits = total != null ? total.value() : 0;
      voPage.setTotal(totalHits);

      List<ArticleVO> vos = response.hits().hits().stream()
              .map(hit -> {
                ArticleVO vo = hit.source();
                if (vo != null) {
                  vo.setId(Integer.valueOf(hit.id()));
                }
                return vo;
              })
              .collect(Collectors.toList());

      voPage.setObjects(vos); // 设置当前页的列表数据
      log.debug("ES查询成功，共命中 {} 条记录，返回 {} 条", totalHits, vos.size());

    } catch (IOException e) {
      log.error("Elasticsearch 分页查询文章时发生IO异常", e);
      // 发生异常时，返回一个安全无害的空分页对象
      voPage.setObjects(Collections.emptyList());
      voPage.setTotal(0L);
    }

    // 5. 设置分页信息并返回
    voPage.setPageNo(query.getPageNo());
    voPage.setPageSize(query.getPageSize());
    return voPage;
  }

  @Override
  public Optional<ArticleSearchPO> findArticleById(String id) {
    if (articleESRepository != null) {
      return articleESRepository.findById(Integer.valueOf(id));
    }
    return Optional.empty();
  }

  @Override
  public boolean existsArticleById(String id) {
    if (articleESRepository != null) {
      return articleESRepository.existsById(Integer.valueOf(id));
    }
    return false;
  }

  @Override
  public void deleteArticleById(String id) {
    if (articleESRepository != null) {
      articleESRepository.deleteById(Integer.valueOf(id));
    }
  }

  @Override
  public Map<String, ArticleStatsVO> getBatchArticleStatsFromRedis(List<String> articleIds) {
    return redisService.getBatchArticleStats(articleIds);
  }

//  @Override
//  public void refillBatchArticleStatsToRedis(Map<String, ArticleStatsVO> refillMap) {
//    redisService.refillBatchArticleStats(refillMap);
//  }

  @Override
  public void deleteArticleStatsFromRedis(String id) {
    redisService.deleteArticleStats(id);
  }

  @Override
  public void incrementArticleStatInRedis(String id, String field, long value) {
    redisService.incrementArticleStat(id, field, value);
  }

  @Override
  public ArticleStatsVO getArticleStatsFromRedis(String id) {
    return redisService.getArticleStats(id);
  }

  @Override
  public void refillArticleStatsToRedis(String id, ArticleStatsVO statsToRefill) {
    redisService.refillArticleStats(id, statsToRefill);
  }

//  @Override
//  public void saveAction(ActionSearchPO action) {
//    actionESRepository.save(action);
//  }

  @Override
  public PageResult<ArticleActionVO> findBehaviorsByUserId(Integer userId, int actionType, ArticleQuery query) {
    PageResult<ArticleActionVO> voPage = new PageResult<>();
    
    // 如果ES客户端不可用，返回空结果
    if (elasticsearchClient == null) {
      log.warn("Elasticsearch客户端不可用，返回空结果");
      voPage.setObjects(Collections.emptyList());
      voPage.setTotal(0L);
      voPage.setPageNo(query.getPageNo());
      voPage.setPageSize(query.getPageSize());
      return voPage;
    }
    
    BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();
    boolQueryBuilder.filter(f -> f.term(t -> t.field("behaviorUserId").value(userId)));
    boolQueryBuilder.filter(f -> f.term(t -> t.field("action").value(actionType)));

    Query finalQuery = boolQueryBuilder.build()._toQuery();

    try {
      SearchResponse<ActionSearchPO> response = elasticsearchClient.search(s -> s
                      .index("lbs_action")
                      .query(finalQuery)
                      .from((query.getPageNo() - 1) * query.getPageSize())
                      .size(query.getPageSize())
                      .sort(so -> so.field(f -> f.field("createTime").order(SortOrder.Desc))),
              ActionSearchPO.class
      );

      TotalHits total = response.hits().total();
      long totalHits = total != null ? total.value() : 0;
      voPage.setTotal(totalHits);

      List<ArticleActionVO> vos = response.hits().hits().stream()
              .map(hit -> {
                ActionSearchPO po = hit.source();
                return (po != null) ? convertActionPoToVo(po) : null;
              })
              .filter(Objects::nonNull)
              .collect(Collectors.toList());

      voPage.setObjects(vos);

    } catch (IOException e) {
      log.error("Elasticsearch search error for user behaviors", e);
      voPage.setObjects(Collections.emptyList());
      voPage.setTotal(0L);
    }

    voPage.setPageNo(query.getPageNo());
    voPage.setPageSize(query.getPageSize());
    return voPage;
  }

  private ArticleActionVO convertActionPoToVo(ActionSearchPO po) {
    if (po == null) {
      return null;
    }

    ArticleActionVO vo = new ArticleActionVO();

    // 1. 设置行为记录本身的ID
    vo.setId(po.getId());

    // 2. 填充操作者的信息
    BeanUtils.copyProperties(po, vo);
    vo.setCreateTime(String.valueOf(new Date()));
    // 直接传递Date对象，@JsonFormat会在Controller层生效

    // 4. 填充关联的文章信息
    vo.setArticleId(po.getArticleId());
    vo.setArticleTitle(po.getArticleTitle());

//    // 5. 填充扩展字段 (当前版本先给默认值)
//    vo.setLikeCount(0); // 评论点赞数，暂时默认为0
//    vo.setReplies(null); // 回复列表，暂时为null

    return vo;
  }
  @Override
  public Set<String> findUniqueLabelsByQuery(ArticleQuery query) {
    log.debug("开始聚合查询唯一标签，查询条件: {}", query);
    
    // 如果ES客户端不可用，返回空集合
    if (elasticsearchClient == null) {
      log.warn("Elasticsearch客户端不可用，返回空集合");
      return Collections.emptySet();
    }

    BoolQuery.Builder boolQueryBuilder = buildArticleQuery(query);

    try {
      SearchResponse<Void> response = elasticsearchClient.search(s -> s
                      .index("lbs_article") // 指定索引
                      .query(boolQueryBuilder.build()._toQuery())
                      .size(0)
                      .aggregations("unique_labels", a -> a
                              .terms(t -> t
                                      .field("articleLabel.keyword")
                                      .size(1000)
                              )
                      ),
              Void.class // 因为不返回文档，所以这里使用 Void.class
      );

      Aggregate aggregate = response.aggregations().get("unique_labels");
      if (aggregate != null && aggregate.isSterms()) {
        // 将聚合结果中的每个桶(bucket)的键(key)提取出来，即为唯一的标签
        return aggregate.sterms().buckets().array().stream()
                .map(bucket -> bucket.key().stringValue())
                .collect(Collectors.toSet());
      }

    } catch (IOException e) {
      log.error("Elasticsearch 聚合查询唯一标签时发生IO异常", e);
    }

    return Collections.emptySet();
  }

  @Override
  public void updateArticle(ArticleSearchPO existingArticle) {
    QueryWrapper<ArticleSearchPO> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("id", existingArticle.getId());
    articleMapper.update(queryWrapper);
  }

  @Override
  public void addArticleAction(ArticleSearchPO actionPO) {
    // 如果ES不可用，则不执行任何操作
    if (actionESRepository != null) {
      System.out.println("保存用户行为");
    } else {
      log.warn("Elasticsearch不可用，跳过保存用户行为");
    }
  }

  @Override
  public void refillBatchArticleStatsToRedis(Map<String, ArticleStatsVO> refillMap) {
    redisService.refillBatchArticleStats(refillMap);
  }


  /**
   * 封装了根据 ArticleQuery 对象构建 Elasticsearch BoolQuery 的通用逻辑。
   * @param query 包含各种过滤条件的前端查询对象
   * @return 构建好的 BoolQuery.Builder 实例
   */
  private BoolQuery.Builder buildArticleQuery(ArticleQuery query) {
    BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();

    if (StringUtils.hasText(query.getArticleTitle())) {
      boolQueryBuilder.must(m -> m.match(t -> t
              .field("articleTitle")
              .query(query.getArticleTitle())
      ));
    }

    if (query.getArticleCategoryId() != null&& query.getArticleCategoryId() != 0) { // 前端用0表示查询所有
      boolQueryBuilder.filter(f -> f.term(t -> t
              .field("articleCategoryId")
              .value(query.getArticleCategoryId())
      ));
    }

    if (StringUtils.hasText(query.getArticleLabel())) {
      boolQueryBuilder.filter(f -> f.term(t -> t
              .field("articleLabel")
              .value(query.getArticleLabel())
      ));
    }

    List<Integer> statusList = query.getArticleStatus();
    if (statusList != null && !statusList.isEmpty()) {
      // 过滤掉代表"全部"的特殊值 0
      List<Integer> actualStatusesToQuery = statusList.stream()
              .filter(status -> status != 0)
              .collect(Collectors.toList());

      if (!actualStatusesToQuery.isEmpty()) {
        List<FieldValue> statusValues = actualStatusesToQuery.stream()
                .map(FieldValue::of)
                .collect(Collectors.toList());

        boolQueryBuilder.filter(f -> f.terms(t -> t
                .field("articleStatus")
                .terms(ts -> ts.value(statusValues))
        ));
      }
    }

    if (query.getSource() != null&&query.getSource()!=0) {
      boolQueryBuilder.filter(f -> f.term(t -> t
              .field("source")
              .value(query.getSource())
      ));
    }

    if (query.getActivityId() != null && query.getActivityId() != 0) {
      boolQueryBuilder.filter(f -> f.term(t -> t
              .field("activityId")
              .value(query.getActivityId())
      ));
    }

    if (query.getUserId() != null) {
      boolQueryBuilder.filter(f -> f.term(t -> t
              .field("userId")
              .value(query.getUserId())
      ));
    }

    if (query.getUpdateStartTime() != null || query.getUpdateEndTime() != null) {
      boolQueryBuilder.filter(f -> f.range(r -> {
        r.field("updateTime");
        if (query.getUpdateStartTime() != null) {
          // gte: Greater Than or Equal To (大于等于)
          r.gte(JsonData.of(query.getUpdateStartTime().getTime()));
        }
        if (query.getUpdateEndTime() != null) {
          // lte: Less Than or Equal To (小于等于)
          r.lte(JsonData.of(query.getUpdateEndTime().getTime()));
        }
        return r;
      }));
    }

    if (StringUtils.hasText(query.getLatitude()) && StringUtils.hasText(query.getLongitude())) {

      log.debug("添加 geo_distance 过滤器：中心点[{},{}], 固定半径[5km]",
              query.getLatitude(), query.getLongitude());

      try {
        double lat = Double.parseDouble(query.getLatitude());
        double lon = Double.parseDouble(query.getLongitude());

        boolQueryBuilder.filter(f -> f.geoDistance(gd -> gd
                .field("location")
                // 核心改动：将距离硬编码为 "5km"
                .distance("5km")
                .location(loc -> loc
                        .latlon(ll -> ll
                                .lat(lat)
                                .lon(lon)
                        )
                )
        ));
      } catch (NumberFormatException e) {
        log.warn("无法解析经纬度字符串: lat={}, lon={}", query.getLatitude(), query.getLongitude(), e);
      }
    }

    return boolQueryBuilder;
  }
  
  @Override
  public PageResult<ArticleActionVO> findBehaviorsByArticleId(String articleId, int actionType, ArticleQuery query) {
    PageResult<ArticleActionVO> voPage = new PageResult<>();
    log.debug("根据文章id查询文章互动列表:{}",articleId);
    
    // 如果ES客户端不可用，返回空结果
    if (elasticsearchClient == null) {
      log.warn("Elasticsearch客户端不可用，返回空结果");
      voPage.setObjects(Collections.emptyList());
      voPage.setTotal(0L);
      voPage.setPageNo(query.getPageNo());
      voPage.setPageSize(query.getPageSize());
      return voPage;
    }
    
    // 1. 构建ES查询条件
    BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();
    // 关键条件一：根据文章ID过滤
    boolQueryBuilder.filter(f -> f.term(t -> t.field("articleId").value(articleId)));
    // 关键条件二：根据行为类型过滤
    boolQueryBuilder.filter(f -> f.term(t -> t.field("action").value(actionType)));

    Query finalQuery = boolQueryBuilder.build()._toQuery();

    try {
      // 2. 执行ES搜索
      SearchResponse<ActionSearchPO> response = elasticsearchClient.search(s -> s
                      .index("lbs_action")
                      .query(finalQuery)
                      .from((query.getPageNo() - 1) * query.getPageSize())
                      .size(query.getPageSize())
                      .sort(so -> so.field(f -> f.field("createTime").order(SortOrder.Desc))),
              ActionSearchPO.class
      );

      // 3. 解析并封装返回结果
      TotalHits total = response.hits().total();
      long totalHits = total != null ? total.value() : 0;
      voPage.setTotal(totalHits);

      List<ArticleActionVO> vos = response.hits().hits().stream()
              .map(hit -> {
                ActionSearchPO po = hit.source();
                return (po != null) ? convertActionPoToVo(po) : null;
              })
              .filter(Objects::nonNull)
              .collect(Collectors.toList());

      voPage.setObjects(vos);

    } catch (IOException e) {
      log.error("Elasticsearch查询文章行为时出错, articleId: {}", articleId, e);
      voPage.setObjects(Collections.emptyList());
      voPage.setTotal(0L);
    }

    // 4. 设置分页信息并返回
    voPage.setPageNo(query.getPageNo());
    voPage.setPageSize(query.getPageSize());
    return voPage;
  }

}